/**
 * 单例设计模式
 * 基于单独的实例，来管理某一个模块中的内容，实现模块之间的独立划分，但是也可以实现模块之间方法的调用
 */

// 模块A
var ModuleA = (function () {
  var data = [];

  function changdata() {
    // ...
  }

  function bindHtml() {
    // ...
  }

  window.bindHtml = bindHtml; // 将bindHtml暴露出去，往window上挂载
  // 将bindHtml暴露出去，赋值给ModuleA，ModuleA为全局变量，通过调用ModuleA.bindHtml可以实现互相调用
  return {
    bindHtml
  }
})();

// 模块B
var ModuleB = (function () {
  var data = [];
  function changdata() {
    // ...
  }
})();

// 举例说明
let searchModule = (function () {
  let body = document.body;
  function queryData() { }
  function bindHtml() { }
  function handle() { }
  return {
    init: function () {
      queryData();
      bindHtml();
      handle()
    }
  }
})();
// 执行init函数，实现初始化
searchModule.init();

/**
 * 构造器模式
 */
class ModuleA {
  constructor() {
    this.arr = [];
  }
  // 往原型上写
  change(val) {
    this.arr.push(val)
  }
}
let m1 = new ModuleA;
let m2 = new ModuleA;
// new 的时候会产生闭包


/**
 * 工厂模式
 * 项目：一个产品  调用数据库，根据量级或者业务的不同等因素，我们需要让产品切换调用到不同的数据库中，
 * 工厂模式：可以帮助我们实现调用的切换，或者实现一些中转的处理
 * jQuery中$()选择器借助工厂模式
 */
function factory(options) {
  options = options || {};
  let {
    type,
    payload
  } = options;
  if (type === 'array') {
    // A逻辑
  } else {
    // B逻辑
  }
}

/**
 * Observer 观察者模式
 */
// 目标应该具备对观察者的管理能力，增删改查
// 每个观察者都应该具备update方法，用于在通知的消息，到达的时候进行更新
// 观察者模式，vue2.0响应式原理
class Observer {
  update() {
    // 消息触达，通知update执行
  }
}
// 创建观察者
let o1 = new Observer;
// 目标
class ObserverList {
  constructor() {
    this.observerList = []
  }
  add(observer) {
    this.observerList.push(observer);
    return this
  }
  remove(observer) {
    this.observerList = this.observerList.filter(ob => ob !== observer);
    return
  }
  get(index) {
    return this.observerList[index]
  }
  count() {
    return this.observerList.length
  }
}
class Subject {
  observers = new ObserverList;
  add(observer) {
    this.observers.add(observer)
  }
  remove(observer) {
    this.observers.remove(observer)
  }
  notify(...params) {
    for (let i = 0; i < this.observers.count(); i++) {
      let item = this.observers.get(i);
      item.update(...params)
    }
  }
}

let sub = new Subject;
sub.add(new Observer);
sub.add(new Observer);
sub.add(new Observer);
setTimeout(() => {
  sub.notify('你好')
}, 1000);

/**
 * 中介者模式
 */
// A ->中介者->B

/**
 * {
 *  'XXX':[
 *    callback,
 *    context
 *  ]
 * }
 */
let mediator = (function () {
  let topics = {};

  // 订阅：订阅A组件中的某个方法
  let subscribe = function subscribe(topic) {
    !topics[topic] ? topics[topic] = [] : null;
    topics[topic].push({
      callback,
      context: this
    });
  };
  // 发布： B组件中到某个阶段，可以通知之前订阅的方法执行
  let publish = function publish(topic, ...params) {
    if (!topics[topic]) return;
    topics[topic].forEach(item => {
      let {
        callback,
        context
      } = item;
      callback.call(context, ...params);
    })
  }

  return {
    subscribe,
    publish
  }
})();

/**
 * 发布订阅设计模式（观察者模式的升级）
 *  发布一个计划，并且向计划中订阅一个个方法
 *  当触发某个事件或者到达了某个阶段，我们可以通知计划中订阅的方法按照顺序依次执行
 * 灵感：事件池，body.addEventListener，body.removeEventListener
 * vue中事件总线，默认创建一个自定义事件池，（一个页面只有一个）
 *  添加订阅方法：$on('AA', fn1)
 *  通知订阅方法执行 $emit('AA')
 */

// 第一版：不支持自定义事件，且一个页面只有一个事件池  [单例设计模式]
let sub = (function () {
  // 创建自定义事件池
  let pond = [];

  // 订阅/移除订阅/通知执行
  const on = function on(func) {
    // 去重处理
    for(let i = 0; i < pond.length; i++) {
      if(pond[i] === func) {
        return
      }
    }
    pond .push(func)
  };
  const off = function off(func) {
    for(let i = 0; i < pond.length; i++) {
      if(pond[i] === func) {
        pond.splice(i, 1);
        break
      }
    }
  };
  const fire = function fire(...params) {
    for(let i = 0; i < pond.length; i++) {
      let funcItem = pond[i];
      funcItem(...params);
    }
  };

  return {
    on,
    off,
    fire
  }
})();
